package com.googlecode.imanager.core.base.style;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import com.googlecode.imanager.client.Application;
import com.googlecode.imanager.client.Document;
import com.googlecode.imanager.client.Manager;
import com.googlecode.imanager.client.bridge.IComponent;
import com.googlecode.imanager.client.bridge.style.IStyle;
import com.googlecode.imanager.client.bridge.style.IThemeHandler;
import com.googlecode.imanager.client.style.Theme;
import com.googlecode.imanager.client.style.listener.StyleAttribute;
import com.googlecode.imanager.client.style.listener.StyleWrapper;
import com.googlecode.imanager.client.util.HasTheme;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class ComputedTheme implements IThemeHandler {
    
    /* TODO -reutilizar o ComputedTheme pai em vez de recomputar tudo
     *      -fazer peguar o ComputedStyle do ComputedTheme pai se o meu "theme" for NULL
     */

    private int updateCount;
    private Map<String, StyleMap> styles;
    private HasTheme owner;

    public ComputedTheme(HasTheme owner) {
        styles = new HashMap<String, StyleMap>(32);
        this.owner = owner;
    }

    public HasTheme getOwner(){
        return owner;
    }

    public void onThemeAttach(Theme theme) {
        reset();
        if (theme != null){
            for (String controlClassName : theme.getRegisteredControlClassNames()){
                for (StyleWrapper styleWrapper : theme.getWrappers(controlClassName)){
                    ComputedStyle dest = getNormalStyle(controlClassName, styleWrapper.getStateFlags());                    
                    dest.mergeWith(styleWrapper.getStyle());                    
                }
            }
        }
    }

    public void onThemeDetach(Theme theme) {
        reset();
    }
    
    protected Theme getDefaultTheme(){
        return (Theme) Manager.getInstance().getDefaultTheme(((IComponent)owner).getImplementationType());
    }
    
    protected void reset(){
        for (StyleMap map : styles.values()){
            map.normal.clear();
            map.mixed.clear();
        }
        
        Application app;
        
        if (owner instanceof Application){
            app = (Application) owner;
        } else {
            app = ((Document) owner).getApplication();
        }
        
        // copy from default theme
        {
            Theme theme = getDefaultTheme();            
            for (String controlClassName : theme.getRegisteredControlClassNames()){
                for (StyleWrapper styleWrapper : theme.getWrappers(controlClassName)){
                    ComputedStyle dest = getNormalStyle(controlClassName, styleWrapper.getStateFlags());                    
                    dest.copyFrom(styleWrapper.getStyle());                    
                }
            }
        }
        
        if (owner instanceof Document){
            Document doc = (Document) owner;
            do {
                
                Theme theme = doc.getTheme();
                if (theme != null){
                    for (String controlClassName : theme.getRegisteredControlClassNames()){
                        for (StyleWrapper styleWrapper : theme.getWrappers(controlClassName)){
                            ComputedStyle dest = getNormalStyle(controlClassName, styleWrapper.getStateFlags());                    
                            dest.mergeWith(styleWrapper.getStyle());                    
                        }
                    }
                }
                
                if (doc.hasParent()){
                    doc = doc.getParent().getDocument();
                } else {
                    doc = null;
                }
            } while (doc != null);
        }
        
        Theme theme = app.getTheme();
        if (theme != null){
            for (String controlClassName : theme.getRegisteredControlClassNames()){
                for (StyleWrapper styleWrapper : theme.getWrappers(controlClassName)){
                    ComputedStyle dest = getNormalStyle(controlClassName, styleWrapper.getStateFlags());                    
                    dest.mergeWith(styleWrapper.getStyle());                    
                }
            }
        }
        
        updateCount++;
    }
        
    public void destroy(){
        for (StyleMap map : styles.values()){
            map.normal.clear();
            map.normal = null;
            map.mixed.clear();
            map.mixed = null;
        }
        styles.clear();
        updateCount++;
    }

    private StyleMap getStyleMap(String controlClassName){
        StyleMap map = styles.get(controlClassName);
        if (map == null){
            map = new StyleMap(controlClassName);
            styles.put(controlClassName, map);
        }
        return map;
    }

    private ComputedStyle getNormalStyle(String controlClassName, int stateFlags){
        StyleMap map = getStyleMap(controlClassName);
        ComputedStyle style = map.normal.get(stateFlags);
        
        if (style == null){
            style = new ComputedStyle();
            map.normal.put(stateFlags, style);
        }
        
        return style;
    }
    
    public void computeStyle(ComputedStyle dest, String controlClassName, int stateFlags){
        if (controlClassName == null){
            throw new IllegalArgumentException("controlClassName must be != null");
        }
        
        StyleMap map = getStyleMap(controlClassName);
        MixedStyle style = map.mixed.get(stateFlags);
        
        if (style == null){
            style = new MixedStyle(stateFlags);
            map.mixed.put(style.stateFlags, style);
        }
        
        if (stateFlags != 0){
            MixedStyle defaultStyle = map.mixed.get(0);

            if (defaultStyle == null){
                defaultStyle = new MixedStyle(0);
                map.mixed.put(defaultStyle.stateFlags, defaultStyle);

                if (defaultStyle.updateCount != updateCount){
                    updateStyleByHierarchy(defaultStyle, map);
                }
            }

            dest.copyFrom(defaultStyle);

            for (Entry<Integer, MixedStyle> entry : map.mixed.entrySet()){
                if ((entry.getKey() & stateFlags) != 0){

                    // merge with one style of the specified states
                    dest.mergeWith(entry.getValue());
                }
            }

        } else {
            if (style.updateCount != updateCount){
                updateStyleByHierarchy(style, map);
            }
            dest.copyFrom(style);
        }
    }

    private void updateStyleByHierarchy(MixedStyle style, StyleMap map) {        
        String superclass = Manager.getInstance().getControlSuperClass(map.controlClassName);
        if (superclass != null){
            
            StyleMap parentMap = getStyleMap(superclass);
            MixedStyle parent = parentMap.mixed.get(0);
            
            if (parent == null){
                parent = new MixedStyle(0);
                parentMap.mixed.put(parent.stateFlags, parent);
            }

            if (parent.updateCount != updateCount){
                updateStyleByHierarchy(parent, parentMap);
            }
        
            for (Entry<Integer, MixedStyle> entry : parentMap.mixed.entrySet()){
                MixedStyle target = map.mixed.get(entry.getKey());

                if (target == null){
                    target = new MixedStyle(entry.getKey());
                    map.mixed.put(target.stateFlags, target);
                }
                
                target.updateCount = updateCount;
                target.copyFrom(entry.getValue());
            }
        }
                
        for (Entry<Integer, ComputedStyle> entry : map.normal.entrySet()){
            MixedStyle target = map.mixed.get(entry.getKey());

            if (target == null){
                target = new MixedStyle(entry.getKey());
                map.mixed.put(target.stateFlags, target);
            }
            
            if (target.updateCount != updateCount){
                target.updateCount = updateCount;
                target.copyFrom(entry.getValue());
            } else {
                target.mergeWith(entry.getValue());
            }
        }
        
        if (style.updateCount != updateCount){
            style.updateCount = updateCount;
            style.copyFrom(map.normal.get(0));
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> void onStyleAttributeChanged(StyleWrapper styleWrapper,
            StyleAttribute<T> attribute, T attributeValue) {
        
        ComputedStyle style = getNormalStyle(styleWrapper.getControlClassName(), styleWrapper.getStateFlags());        
        ComputedAttribute<T> comp = (ComputedAttribute<T>) attribute.getAttributeValue(style);
        doStyleAttributeChanged(styleWrapper, comp, attributeValue, attribute);
        
        updateCount++;
    }
    
    protected <T> void doStyleAttributeChanged(StyleWrapper wrapper, 
            ComputedAttribute<T> dest, T value, StyleAttribute<T> component){
        
        if (owner instanceof Document){
            inheritFromTheme(wrapper, (Document) owner, dest, component);
        } else {
            inheritFromTheme(wrapper, (Application) owner, dest, component);
        }
        
        IStyle style = owner.getTheme().getStyle(wrapper.getControlClassName(), wrapper.getStateFlags());
        
        if (style != null){
            dest.mergeWith(component.getAttributeValue(style));
        }
    }
    
    protected <T> void inheritFromTheme(StyleWrapper wrapper, Document doc, ComputedAttribute<T> dest, StyleAttribute<T> component){
        Document parent;

        if (doc.hasParent()){
            parent = doc.getParent().getDocument();
        } else {
            parent = null;
        }

        if (parent != null){
            inheritFromTheme(wrapper, parent, dest, component);                
        } else {
            inheritFromTheme(wrapper, doc.getApplication(), dest, component);
        }

        if (doc.getTheme() != null){
            IStyle style = doc.getTheme().getStyle(wrapper.getControlClassName(), wrapper.getStateFlags());

            if (style != null){
                dest.mergeWith(component.getAttributeValue(style));
            }
        }
    }
    
    protected <T> void inheritFromTheme(StyleWrapper wrapper, Application app, ComputedAttribute<T> dest, StyleAttribute<T> component) {
        
        IStyle style = getDefaultTheme().getStyle(
                wrapper.getControlClassName(), wrapper.getStateFlags());
        
        if (style != null){
            dest.copyFrom(component.getAttributeValue(style));
        } else {
            dest.copyFrom(null);
        }

        if (app.getTheme() != null){
            style = app.getTheme().getStyle(wrapper.getControlClassName(), wrapper.getStateFlags());
            if (style != null){
                dest.mergeWith(component.getAttributeValue(style));
            }
        }
    }

    
    /**
     * @author wellsantos@wellsantos.com
     * @since 0.01
     */
    private static class StyleMap {
        
        private String controlClassName;
        private Map<Integer,ComputedStyle> normal;
        private Map<Integer,MixedStyle> mixed;
        
        public StyleMap(String controlClassName) {
            this.controlClassName = controlClassName;
            normal = new HashMap<Integer, ComputedStyle>(8);
            mixed = new TreeMap<Integer, MixedStyle>(MixedComparator.INSTANCE);
        }
        
    }
    
    
    /**
     * @author wellsantos@wellsantos.com
     * @since 0.01
     */
    private static class MixedStyle extends ComputedStyle {
        int updateCount = -1;
        Integer stateFlags;
        
        public MixedStyle(int stateFlags) {
            this.stateFlags = stateFlags;
        }
        
    }

    
    /**
     * @author wellsantos@wellsantos.com
     * @since 0.01
     */
    private static class MixedComparator implements Comparator<Integer> {

        public static final MixedComparator INSTANCE = new MixedComparator();
        
        private MixedComparator() {
        }
        
        @Override
        public int compare(Integer o1, Integer o2) {
            int a = o1.intValue();
            int b = o2.intValue();
            
            /* 'a' > 'b' when 'a' has more bits than 'b'
             * See:
             *      a = 0x000110
             *      b = 0x010000
             */
            
            int acount = 0;
            int bcount = 0;
            
            while (a != 0 
                    && b != 0) {
                
                acount += a & 1;
                bcount += b & 1;
                
                a = a >>> 1;
                b = b >>> 1;
            }
            
            int res = acount - bcount;
            if (res != 0){
                return res;
            }
            
            /* Otherwise, 'a' > 'b' if it's value is bigger than 'b'
             * See:
             *      a = 0x001000
             *      b = 0x000010
             */

            return a - b;
        }
        
    }
    
}